﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.UI.WebControls;
using Niknak.Services.ServiceCore;
using Niknak.Services.TaskScheduler;
using Niknak.Services.TaskScheduler.Interfaces.DataModel;
using Niknak.Services.TaskScheduler.UI.Web;

namespace Niknak.Services.TaskScheduler.UI.WebControls
{
    public class TaskScheduler<ExceptionHandlerType, ScheduledTaskType, AuditLogType, AuditLogEntryType> : EmbeddedUserControl
        where ExceptionHandlerType : IExceptionHandler, new()
        where ScheduledTaskType : IScheduledTask, new()
        where AuditLogType : IAuditLog<AuditLogEntryType>, new()
        where AuditLogEntryType : IAuditLogEntry
    {
        #region Controls
        protected global::System.Web.UI.WebControls.PlaceHolder uiPlhServiceInfo;
        protected global::System.Web.UI.WebControls.Literal uiLitCurrentTime;
        protected global::System.Web.UI.WebControls.Literal uiLitStatus;
        protected global::System.Web.UI.WebControls.Literal uiLitCurrentTasks;
        protected global::System.Web.UI.WebControls.Button uiBtnReset;
        protected global::System.Web.UI.WebControls.Button uiBtnRestart;
        protected global::System.Web.UI.WebControls.Literal uiLitRestartResult;
        protected global::System.Web.UI.WebControls.Literal uiLitResetResult;
        protected global::System.Web.UI.WebControls.MultiView uiMlvTasks;
        protected global::System.Web.UI.WebControls.View uiViwTasksList;
        protected global::System.Web.UI.WebControls.Repeater uiRptAllTasks;
        protected global::System.Web.UI.WebControls.View uiViwEditTask;
        protected global::System.Web.UI.WebControls.DropDownList uiDdlTaskType;
        protected global::System.Web.UI.WebControls.TextBox uiTxtName;
        protected global::System.Web.UI.WebControls.TextBox uiTxtDisplayName;
        protected global::System.Web.UI.WebControls.TextBox uiTxtCLRType;
        protected global::System.Web.UI.WebControls.TextBox uiTxtStartArguments;
        protected global::System.Web.UI.WebControls.CheckBoxList uiCbxRunOnDays;
        protected global::System.Web.UI.WebControls.TextBox uiTxtStartDate;
        protected global::System.Web.UI.WebControls.TextBox uiTxtStartTime;
        protected global::System.Web.UI.HtmlControls.HtmlTableRow uiTrEndDate;
        protected global::System.Web.UI.WebControls.TextBox uiTxtEndDate;
        protected global::System.Web.UI.HtmlControls.HtmlTableRow uiTrEndTime;
        protected global::System.Web.UI.WebControls.TextBox uiTxtEndTime;
        protected global::System.Web.UI.WebControls.TextBox uiTxtIntervalSimple;
        protected global::System.Web.UI.HtmlControls.HtmlTableRow uiTrDrift;
        protected global::System.Web.UI.WebControls.TextBox uiTxtAllowableDrift;
        protected global::System.Web.UI.HtmlControls.HtmlTableRow uiTrTerminate;
        protected global::System.Web.UI.WebControls.CheckBox uiCbxTerminate;
        protected global::System.Web.UI.WebControls.CheckBox uiCbxEnableCompletedEvent;
        protected global::System.Web.UI.WebControls.CheckBox uiCbxEnableLogging;
        protected global::System.Web.UI.WebControls.CheckBox uiCbxIsActive;
        protected global::System.Web.UI.WebControls.Button uiBtnSave;
        protected global::System.Web.UI.WebControls.Button uiBtnRemove;
        protected global::System.Web.UI.WebControls.View uiViwSchedule;
        protected global::System.Web.UI.WebControls.Literal uiLitScheduleTitle;
        protected global::System.Web.UI.WebControls.Repeater uiRptSchedule;
        protected global::System.Web.UI.HtmlControls.HtmlTableRow uiTrIntervalSimple;
        protected global::System.Web.UI.HtmlControls.HtmlTableRow uiTrIntervalAdvanced;
        protected global::System.Web.UI.WebControls.TextBox uiTxtIntervalCustom;
        protected global::System.Web.UI.WebControls.DropDownList uiDdlIntervalType;
        protected global::System.Web.UI.WebControls.PlaceHolder uiPlcCustomInterval;
        #endregion

        private ScheduledTaskType ScheduledTask = new ScheduledTaskType();

        private Guid? QsScheduledTaskId
        {
            get
            {
                string rawGuid = Request.QueryString["taskId"];

                if (!string.IsNullOrEmpty(rawGuid))
                {
                    Guid outGuid;
                    if (Guid.TryParse(rawGuid, out outGuid))
                        return outGuid;
                }

                return null;
            }
        }

        private Guid? QsScheduleId
        {
            get
            {
                string rawGuid = Request.QueryString["sched"];

                if (!string.IsNullOrEmpty(rawGuid))
                {
                    Guid outGuid;
                    if (Guid.TryParse(rawGuid, out outGuid))
                        return outGuid;
                }

                return null;
            }
        }

        public TaskScheduler()
            : base("TaskScheduler.ascx")
        {
        }

        protected void Page_Load(object sender, EventArgs e)
        {
            uiBtnSave.Click += (s, a) => SaveTask();
            uiBtnRemove.Click += (s, a) =>
            {
                ScheduledTask.RemoveByTaskId(QsScheduledTaskId.Value);
                Response.Redirect(Request.Url.AbsolutePath);
            };
            uiBtnReset.Click += (s, a) =>
            {
                bool result = WcfEndpointHelper<ExceptionHandlerType>.Reset();
                uiLitResetResult.Text = result ? "Command accepted, see Event Log for details" : "Command failed";
            };
            uiBtnRestart.Click += (s, a) =>
            {
                bool result = WcfEndpointHelper<ExceptionHandlerType>.Restart();
                uiLitRestartResult.Text = result ? "Command accepted, see Event Log for details" : "Command failed";
            };
            uiDdlTaskType.SelectedIndexChanged += (s, a) =>
            {
                switch (uiDdlTaskType.SelectedItem.Value)
                {
                    case "1": //simple
                        SetSimpleMode();
                        break;
                    case "2": //advanced
                        SetAdvancedMode();
                        break;
                }
            };
            uiDdlIntervalType.SelectedIndexChanged += (s, a) =>
            {
                uiPlcCustomInterval.Visible = uiDdlIntervalType.SelectedIndex == 0;
            };

            if (!IsPostBack && QsScheduledTaskId.HasValue)
                if (QsScheduledTaskId.Value == Guid.Empty)
                    PopulateNewTask();
                else
                    PopulateTask(QsScheduledTaskId.Value);
            else if (!IsPostBack && QsScheduleId.HasValue)
                PopulateTaskSchedule(QsScheduleId.Value);
            else if (!QsScheduledTaskId.HasValue)
                PopulateTasks();

            uiLitCurrentTime.Text = DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss");
        }

        private void SetSimpleMode()
        {
            uiDdlTaskType.ClearSelection();
            uiDdlTaskType.Items[0].Selected = true;
            uiTrEndDate.Visible = false;
            uiTrEndTime.Visible = false;
            uiTrIntervalSimple.Visible = true;
            uiTrIntervalAdvanced.Visible = false;
            uiTrDrift.Visible = false;
            uiTrTerminate.Visible = false;
        }

        private void SetAdvancedMode()
        {
            uiDdlTaskType.ClearSelection();
            uiDdlTaskType.Items[1].Selected = true;
            uiTrEndDate.Visible = true;
            uiTrEndTime.Visible = true;
            uiTrIntervalSimple.Visible = false;
            uiTrIntervalAdvanced.Visible = true;
            uiTrDrift.Visible = true;
            uiTrTerminate.Visible = true;
        }

        private void UpdateStatus()
        {
            Guid[] runningTaskIds = WcfEndpointHelper<ExceptionHandlerType>.GetRunningTaskIds();

            uiLitStatus.Text = WcfEndpointHelper<ExceptionHandlerType>.IsEndpointOnline() ? "Online" : "Offline";
            uiLitCurrentTasks.Text = string.Join(", ", runningTaskIds.Select(id => ScheduledTask.GetByTaskId(id).Name));
            if (runningTaskIds.Any())
                uiBtnReset.Attributes.Add("onclick", "return confirm('Resetting while tasks are running could result in loss of data and/or unexpected behaviour - continue?');");

            uiBtnReset.Enabled = uiBtnRestart.Enabled = WcfEndpointHelper<ExceptionHandlerType>.IsEndpointOnline();
        }

        private void PopulateTasks()
        {
            List<ScheduledTaskType> tasks = ScheduledTask.GetAllScheduledTasks().Cast<ScheduledTaskType>().OrderBy(st => st.Name).ToList();

            uiRptAllTasks.DataSource = tasks;
            uiRptAllTasks.DataBind();

            Literal uiLitRow, uiLitName, uiLitStartDate, uiLitStartTime, uiLitEndDate, uiLitEndTime, uiLitTerminate, uiLitInterval, uiLitRunOnDays, uiLitLogLink, uiLitLogSchedule, uiLitEditLink;
            ScheduledTaskType task;

            foreach (RepeaterItem item in uiRptAllTasks.Items)
            {
                uiLitRow = item.FindControl("uiLitRow") as Literal;
                uiLitName = item.FindControl("uiLitName") as Literal;
                uiLitStartDate = item.FindControl("uiLitStartDate") as Literal;
                uiLitStartTime = item.FindControl("uiLitStartTime") as Literal;
                uiLitEndDate = item.FindControl("uiLitEndDate") as Literal;
                uiLitEndTime = item.FindControl("uiLitEndTime") as Literal;
                uiLitTerminate = item.FindControl("uiLitTerminate") as Literal;
                uiLitInterval = item.FindControl("uiLitInterval") as Literal;
                uiLitRunOnDays = item.FindControl("uiLitRunOnDays") as Literal;
                uiLitLogLink = item.FindControl("uiLitLogLink") as Literal;
                uiLitLogSchedule = item.FindControl("uiLitLogSchedule") as Literal;
                uiLitEditLink = item.FindControl("uiLitEditLink") as Literal;
                task = tasks[item.ItemIndex];

                uiLitRow.Text = string.Format(uiLitRow.Text, task.IsActive ? "active" : "inactive");
                uiLitName.Text = task.Name;
                uiLitStartDate.Text = task.StartDate;
                uiLitStartTime.Text = task.StartTime;
                uiLitEndDate.Text = task.EndDate;
                uiLitEndTime.Text = task.EndTime;
                uiLitTerminate.Text = task.TerminateAtEndTime ? "Enabled" : "Disabled";
                uiLitInterval.Text = ((Niknak.Services.ServiceCore.InternalEnums.TaskScheduler.IntervalType)task.IntervalType).ToString();
                uiLitRunOnDays.Text = ((Niknak.Services.ServiceCore.InternalEnums.TaskScheduler.DaysOfWeek)(task.RunOnDays)).ToString("G");
                uiLitLogLink.Text = string.Format(uiLitLogLink.Text, task.ScheduledTaskId, new AuditLogType().EditPageUrl);
                uiLitLogSchedule.Text = string.Format(uiLitLogSchedule.Text, task.ScheduledTaskId);
                uiLitEditLink.Text = string.Format(uiLitEditLink.Text, task.ScheduledTaskId);

                switch (task.TaskType)
                {
                    case Niknak.Services.TaskScheduler.Enums.TaskType.Simple:
                        uiLitEndDate.Visible = false;
                        uiLitEndTime.Visible = false;
                        uiLitTerminate.Visible = false;
                        uiLitLogSchedule.Visible = false;
                        break;
                    case Niknak.Services.TaskScheduler.Enums.TaskType.Advanced:
                        break;
                }
            }

            UpdateStatus();
            uiPlhServiceInfo.Visible = true;
            uiMlvTasks.SetActiveView(uiViwTasksList);
        }

        private void PopulateTaskSchedule(Guid taskId)
        {
            ScheduledTaskType scheduledTask = (ScheduledTaskType)ScheduledTask.GetByTaskId(taskId);

            uiLitScheduleTitle.Text = string.Format(uiLitScheduleTitle.Text, scheduledTask.Name);

            AdvancedTask<ExceptionHandlerType, ScheduledTaskType, AuditLogType, AuditLogEntryType> task = new AdvancedTask<ExceptionHandlerType, ScheduledTaskType, AuditLogType, AuditLogEntryType>(scheduledTask);
            DateTime[] iterations = task.Iterations;

            uiRptSchedule.DataSource = iterations;
            uiRptSchedule.DataBind();

            Literal uiLitDate, uiLitTime;

            foreach (RepeaterItem item in uiRptSchedule.Items)
            {
                uiLitDate = item.FindControl("uiLitDate") as Literal;
                uiLitTime = item.FindControl("uiLitTime") as Literal;

                uiLitDate.Text = iterations[item.ItemIndex].ToString("yyyy-MM-dd");
                uiLitTime.Text = iterations[item.ItemIndex].ToString("HH:mm:ss");
            }

            uiMlvTasks.SetActiveView(uiViwSchedule);
        }

        private void PopulateNewTask()
        {
            uiPlhServiceInfo.Visible = false;
            SetSimpleMode();

            uiMlvTasks.SetActiveView(uiViwEditTask);
        }

        private void PopulateTask(Guid taskId)
        {
            ScheduledTaskType task = (ScheduledTaskType)ScheduledTask.GetByTaskId(taskId);

            if (task == null)
                PopulateTasks();

            uiTxtName.Text = task.Name;
            uiTxtDisplayName.Text = task.DisplayName;
            uiTxtStartDate.Text = task.StartDate;
            uiTxtStartTime.Text = task.StartTime;
            uiTxtEndDate.Text = task.EndDate;
            uiTxtEndTime.Text = task.EndTime;
            uiCbxTerminate.Checked = task.TerminateAtEndTime;

            uiDdlIntervalType.ClearSelection();
            uiDdlIntervalType.Items[(int)task.IntervalType].Selected = true;

            if (task.TaskType == Enums.TaskType.Simple)
                uiTxtIntervalSimple.Text = TimeSpan.FromSeconds(task.CustomIntervalSeconds).ToString();
            else if ((ServiceCore.InternalEnums.TaskScheduler.IntervalType)task.IntervalType == InternalEnums.TaskScheduler.IntervalType.Custom)
                uiTxtIntervalCustom.Text = TimeSpan.FromSeconds(task.CustomIntervalSeconds).ToString();
            else
                uiPlcCustomInterval.Visible = false;
            
            uiTxtCLRType.Text = task.CLRType;
            uiTxtStartArguments.Text = task.StartArguments;
            uiTxtAllowableDrift.Text = task.AllowableDrift.ToString();
            uiCbxEnableCompletedEvent.Checked = task.EnableCompletedEvent;
            uiCbxEnableLogging.Checked = task.EnableLogging;
            uiCbxIsActive.Checked = task.IsActive;
            uiBtnRemove.Attributes.Add("onclick", "return confirm('This action cannot be undone. Are you sure?')");

            Niknak.Services.ServiceCore.InternalEnums.TaskScheduler.DaysOfWeek daysOfWeek = (Niknak.Services.ServiceCore.InternalEnums.TaskScheduler.DaysOfWeek)task.RunOnDays;
            foreach (ListItem item in uiCbxRunOnDays.Items)
            {
                if (daysOfWeek.HasFlag((Niknak.Services.ServiceCore.InternalEnums.TaskScheduler.DaysOfWeek)int.Parse(item.Value)))
                    item.Selected = true;
            }

            switch (task.TaskType)
            {
                case Niknak.Services.TaskScheduler.Enums.TaskType.Simple:
                    SetSimpleMode();
                    break;
                case Niknak.Services.TaskScheduler.Enums.TaskType.Advanced:
                    SetAdvancedMode();
                    break;
            }

            uiMlvTasks.SetActiveView(uiViwEditTask);
            uiPlhServiceInfo.Visible = false;
        }

        private void SaveTask()
        {
            ScheduledTaskType task;

            if (QsScheduledTaskId.Value == Guid.Empty)
            {
                task = ScheduledTask.GetAllScheduledTasks().Where(s => s.Name.ToLowerInvariant() == uiTxtName.Text.ToLowerInvariant()).Select(st => (ScheduledTaskType)st).FirstOrDefault();
                if (task != null)
                    throw new Exception("Scheduled task already exists"); //todo: tidy up

                task = new ScheduledTaskType();
                task.ScheduledTaskId = Guid.NewGuid();
            }
            else
                task = (ScheduledTaskType)ScheduledTask.GetByTaskId(QsScheduledTaskId.Value);

            task.Name = uiTxtName.Text;

            if (string.IsNullOrEmpty(task.Name))
                return;

            try
            {
                task.DisplayName = uiTxtDisplayName.Text;
                task.StartTime = DateTime.ParseExact(uiTxtStartTime.Text, "HH:mm:ss", null, System.Globalization.DateTimeStyles.AssumeUniversal).ToUniversalTime().ToString("HH:mm:ss");

                Niknak.Services.ServiceCore.InternalEnums.TaskScheduler.DaysOfWeek daysOfWeek = Niknak.Services.ServiceCore.InternalEnums.TaskScheduler.DaysOfWeek.Unknown;
                foreach (ListItem item in uiCbxRunOnDays.Items)
                    if (item.Selected)
                        Utils.FlagsHelper.Set<Niknak.Services.ServiceCore.InternalEnums.TaskScheduler.DaysOfWeek>(ref daysOfWeek, (Niknak.Services.ServiceCore.InternalEnums.TaskScheduler.DaysOfWeek)int.Parse(item.Value));

                task.RunOnDays = (int)daysOfWeek;
                task.CLRType = uiTxtCLRType.Text;
                task.StartArguments = uiTxtStartArguments.Text;
                task.EnableCompletedEvent = uiCbxEnableCompletedEvent.Checked;
                task.EnableLogging = uiCbxEnableLogging.Checked;
                task.IsActive = uiCbxIsActive.Checked;

                switch (uiDdlTaskType.SelectedItem.Value)
                {
                    case "1": //simple
                        task.TaskType = Niknak.Services.TaskScheduler.Enums.TaskType.Simple;
                        task.CustomIntervalSeconds = (long)TimeSpan.Parse(uiTxtIntervalSimple.Text).TotalSeconds;

                        if (!string.IsNullOrEmpty(uiTxtStartDate.Text))
                            task.StartDate = DateTime.ParseExact(uiTxtStartDate.Text, "yyyy-MM-dd", null, System.Globalization.DateTimeStyles.AssumeUniversal).ToUniversalTime().ToString("yyyy-MM-dd");
                        break;
                    case "2": //advanced
                        task.TaskType = Niknak.Services.TaskScheduler.Enums.TaskType.Advanced;

                        task.IntervalType = uiDdlIntervalType.SelectedIndex;
                        task.CustomIntervalSeconds = 0;
                        if ((ServiceCore.InternalEnums.TaskScheduler.IntervalType)task.IntervalType == InternalEnums.TaskScheduler.IntervalType.Custom)
                            task.CustomIntervalSeconds = (long)TimeSpan.Parse(uiTxtIntervalCustom.Text).TotalSeconds;

                        task.StartDate = DateTime.ParseExact(uiTxtStartDate.Text, "yyyy-MM-dd", null, System.Globalization.DateTimeStyles.AssumeUniversal).ToUniversalTime().ToString("yyyy-MM-dd");
                        task.EndDate = DateTime.ParseExact(uiTxtEndDate.Text, "yyyy-MM-dd", null, System.Globalization.DateTimeStyles.AssumeUniversal).ToUniversalTime().ToString("yyyy-MM-dd");
                        task.EndTime = string.Empty;
                        if (!string.IsNullOrEmpty(uiTxtEndTime.Text))
                            task.EndTime = DateTime.ParseExact(uiTxtEndTime.Text, "HH:mm:ss", null, System.Globalization.DateTimeStyles.AssumeUniversal).ToUniversalTime().ToString("HH:mm:ss");
                        task.TerminateAtEndTime = uiCbxTerminate.Checked;
                        task.AllowableDrift = int.Parse(uiTxtAllowableDrift.Text);

                        if (DateTime.ParseExact(task.EndDate, "yyyy-MM-dd", null, System.Globalization.DateTimeStyles.AssumeUniversal).ToUniversalTime() < DateTime.ParseExact(task.StartDate, "yyyy-MM-dd", null, System.Globalization.DateTimeStyles.AssumeUniversal).ToUniversalTime())
                            task.IsActive = false;
                        break;
                }
            }
            catch
            {
                task.IsActive = false;
            }

            if (string.IsNullOrEmpty(task.CLRType))
                task.IsActive = false;

            task.Save();

            Response.Redirect(Request.Url.AbsolutePath);
        }
    }
}
